Louisville Weather Time Series Analysis

There is a lot of news about climate change and strange temperatures this year. In some areas with have the “bomb cyclone”, and in other we have drought conditions. Living in Louisville I decided to take some time to see what the local data actually shows about trends in weather.

For this analysis I will be using a few packages which you will need loaded in order to follow along:

# List of packages to load:
packages <- c("tidyverse", "lubridate", "tibbletime", "rlang", "dygraphs", "forecast", "zoo", "xts", "stringr")
  
# Check to see whether any packages aren't installed on the computer and install
new_packages <- packages[!(packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) install.packages(new_packages)
  
# Load Neccessary Packages
sapply(packages, require, character.only = TRUE)
rm(new_packages)

Data Collection and Import

The National Climate Data Center (NCDC) and National Centers for Environmental Information (NCEI) provides access to hourly weather data from the National Oceanic and Atmospheric Administration (NOAA) at https://www7.ncdc.noaa.gov/CDO/cdopoemain.cmd?datasetabbv=DS3505&countryabbv=&georegionabbv=&resolution=40 (Valid as of 02/04/2018 but slated for obsolecense in May of 2018). This form is the fastest way to get clean data. The link automatically cleans the data into a person readable format. So for ease of use I put in a request for the data I wanted and then downloaded the txt files.

Downloading data from the site

I put in the following requests on the site:

For Lexington Data * US, Kentucky, Bluegrass Airport, 1973_01_01_00 to 2008_01_01_23.

For Bowman Field Data there is some special work required because the name of the field changed in the middle of the data set from 2000 to 2003. * US, Kentucky, Bowman Fld, 2000_01_01_00 to 2003_12_31_23. * US, Kentucky, Bowman Filed Airport, 1973_01_01_00 to 2008_01_01_23.

After a few minutes the website processed process the file and send an email. Download the file to the working directory and then import the data to R.

# Load Lexington Weather Data
lexington.file <- c("5157227547406dat.txt")
lex.df <- read.delim(lexington.file,header=TRUE, sep = "", as.is = TRUE, fill = TRUE)
rm(lexington.file)
# Make the YR..MODAHRMN easier to read
colnames(lex.df)[3] <- "DATE_TIME"
# Load Bowman Field Weather Data
bf.file1 <- c("1973_01_01-2018_01_01-BowmanField.txt")
bf.file2 <- c("2000_01_01-2003_12_31-BowmanField.txt")
bf.df1 <- read.delim(bf.file1,header=TRUE, sep = "", as.is = TRUE, fill = TRUE)
bf.df2 <- read.delim(bf.file2,header=TRUE, sep = "", as.is = TRUE, fill = TRUE)
bf.df <- full_join(bf.df1,bf.df2)
rm(bf.file1,bf.file2,bf.df1,bf.df2)
# Make the YR..MODAHRMN easier to read
colnames(bf.df)[3] <- "DATE_TIME"
# Arrange Bowman Field Data by date because we are merging two sets of date data
# and need the dates to be in ascending order
bf.df <- bf.df %>% arrange(DATE_TIME)

Clean up values that have special meaning

Some of the values are non-numeric and have special meaning.

In the cloud ceiling column the value 722 means unlimted (aka no cloud ceiling).

In all columns a * represents an NA value.

clean_ncdc_df <- function(in.df) {
  in.df[in.df[,"CLG"]=="722","CLG"] <- Inf
  # Replace missing values marked by * with NA
  for(col in names(in.df)) {
    in.df[,col] <- str_replace_all(in.df[,col],"^[*]+$","NA")
    in.df[in.df[,col]=="NA",col] <- NA
  }
  return(in.df)
}
bf.df <- clean_ncdc_df(bf.df)
lex.df <- clean_ncdc_df(lex.df)
rm(clean_ncdc_df)

Fix variable types in the data

Two operations need to be performed to fix the variables types.

First, the DATE_TIME field should be in date format.

Second, the numeric items should be in numeric format

  • Note: Any fields with a character will be coerced to NA. In this case it is a good thing because a character in a numeric field indicates there was some anomaly with the data.
bf.df$DATE_TIME <- ymd_hm(bf.df$DATE_TIME)
lex.df$DATE_TIME <- ymd_hm(lex.df$DATE_TIME)
  
# The following fields are numberic data (or close enough)
numeric.var <- c("USAF", "WBAN","DIR","SPD","GUS","CLG","VSB","TEMP","DEWP","ALT","PCP01","SD")
for(col in numeric.var) {
  bf.df[col] <- as.numeric(unlist(bf.df[col]))
}
for(col in numeric.var) {
  lex.df[col] <- as.numeric(unlist(lex.df[col]))
}
rm(numeric.var)

Only keep the columns needed for the analysis

For this analysis we will only be using numeric data. We will keep:

  • Date/Time (the time index…)
  • Temperature
  • Dewpoint
  • Wind Direction
  • Wind Speed
  • Gust Speed
  • Cloud Ceiling
  • Visibility
  • Precipitation in Previous Hour
colsToKeep <- c("DATE_TIME","TEMP","DEWP","DIR","SPD","GUS","CLG","VSB","PCP01")
bf.df <- bf.df %>% select(colsToKeep)
lex.df <- lex.df %>% select(colsToKeep)
rm(colsToKeep)  

Make this into a tibbletime object for analysis

Converting into a tibbletime object is apparently the new way to use time series in R as of early 2018.

bf.tt <- as_tbl_time(bf.df,index = DATE_TIME)
lex.tt <- as_tbl_time(lex.df, index = DATE_TIME)

Now let’s plot some of the data to make sure this is working. Let’s start with temperature because it is realatively clean data.

par(mfrow=c(2,1))
plot(x = bf.tt$DATE_TIME, y = bf.tt$TEMP, type = "n", main = "Bowman Field") +
  lines(x = bf.tt$DATE_TIME, y = bf.tt$TEMP)
integer(0)
plot(x = lex.tt$DATE_TIME, y = lex.tt$TEMP, type = "n", main = "Lexington") +
  lines(x = lex.tt$DATE_TIME, y = lex.tt$TEMP)
integer(0)

Huzzah! We have two sets of data from 1973 through 2017!

Prepare data for Time Series Analysis

Make the data set regular

Many time series functions use integer inputs to determine the length of a period. For example: one year is 365 daily values, one year is 24*365.25 hourly values, etc. Because these functions use an integer number of values as a period, any missing or extra values will cause problems. For instance, what happens if one year has exactly 385 daily values, but another year has 300 daily values? If we perform a moving average with a window of a 365 days there will be problems in the moving average.

Check for regular data

Let’s first see if there are any missing hours in a year.

bf.tt %>% 
  collapse_by('yearly') %>%
  group_by(DATE_TIME) %>%
  select(DATE_TIME,TEMP) %>%
  summarise(length = length(TEMP))
# A time tibble: 46 x 2
# Index: DATE_TIME
   DATE_TIME           length
   <dttm>               <int>
 1 1973-12-31 23:00:00   8360
 2 1974-12-31 22:00:00   8109
 3 1975-12-31 23:00:00   7971
 4 1976-12-31 23:00:00   8445
 5 1977-12-31 23:00:00   8888
 6 1978-12-31 23:00:00   8793
 7 1979-12-31 23:00:00   8799
 8 1980-12-31 23:00:00   8793
 9 1981-12-31 23:00:00   8898
10 1982-12-31 23:00:00   8824
# ... with 36 more rows

Notice that each year has a different length. This data is definitely not regular. There is a difference in length of almost 1,000 hours here.

Now let’s see if there are any hours with multiple readings.

bf.tt %>% 
  collapse_by('hourly') %>%
  group_by(DATE_TIME) %>%
  select(DATE_TIME,TEMP) %>%
  summarise(count = n()) %>%
  collapse_by('yearly') %>%
  group_by(DATE_TIME) %>%
  summarise(min_val_per_hour = min(count),max_val_per_hour = max(count))
# A time tibble: 46 x 3
# Index: DATE_TIME
   DATE_TIME           min_val_per_hour max_val_per_hour
   <dttm>                         <dbl>            <dbl>
 1 1973-12-31 23:00:00             1.00             4.00
 2 1974-12-31 22:00:00             1.00             3.00
 3 1975-12-31 23:00:00             1.00             4.00
 4 1976-12-31 23:00:00             1.00             4.00
 5 1977-12-31 23:00:00             1.00             4.00
 6 1978-12-31 23:00:00             1.00             4.00
 7 1979-12-31 23:00:00             1.00             3.00
 8 1980-12-31 23:00:00             1.00             4.00
 9 1981-12-31 23:00:00             1.00             4.00
10 1982-12-31 23:00:00             1.00             5.00
# ... with 36 more rows

Again our data isn’t regular! Some hours have 1 value associated with them, and some have up to 5 values associated with them!

Fix irregular data

The weather data has BOTH missing hourly values AND extra hourly values.

Now, let’s fill in any missing rows so that our period of one [solar] year is correct.

## Note: this is old code. 
# Need to update and use tibbletime::create_series(start ~ end, '1 hour')
tt_add_missing_rows <- function(in.tbl_time, by = 'hour') {
  # Programatically find the index column
  index_column <- attributes(in.tbl_time)$index_quo[[2]]
  
  # Programatically find the first and last date/time in the index
  start.idx <- start(in.tbl_time[,paste0(index_column)][[1]])
  end.idx <- end(in.tbl_time[,paste0(index_column)][[1]])
  
  start <- in.tbl_time[start.idx,paste0(index_column)][[1]][[1]]
  end <- in.tbl_time[end.idx,paste0(index_column)][[1]][[1]]
  
  seq.vec <- seq(start,end,by=by)
  seq.df <- data.frame(seq.vec)
    # Make the date/time column the same as for the other vector 
    # so that merge by will auto detect.
    names(seq.df)[1] <- paste(index_column)
  
  out.tbl_time <- merge(in.tbl_time,seq.df,all = TRUE)
  out.tbl_time <- as_tbl_time(out.tbl_time, index = DATE_TIME)
}
bf.tt <- tt_add_missing_rows(bf.tt, by = 'hour')
lex.tt <- tt_add_missing_rows(lex.tt, by = 'hour')

With all the rows there, let’s remove extra hourly values by aggregating the hour using a mean of all values for that hour. (Note: There are half a million observations here…this takes a minute to aggregate…)

tt_period_apply <- function(in.tbl_time, in.period, in.func = mean, na_rm = TRUE) {
  # Programatically find the index column
  index_column <- attributes(in.tbl_time)$index_quo[[2]]
  # Collapse by period, group by index, then summarise by function.
  out.tbl_time <- in.tbl_time %>%
    collapse_by(in.period) %>%
    group_by(!!! sym(index_column)) %>%
    summarise_if(is.numeric, in.func, na.rm = na_rm)
}
bf.tt <- tt_period_apply(bf.tt, 'hourly', mean, na_rm = TRUE)
lex.tt <- tt_period_apply(lex.tt, 'hourly', mean, na_rm = TRUE)

Check again for regular data

Let’s validate that we now have regular data (full of NA’s).

bf.tt %>% 
  collapse_by('yearly') %>%
  group_by(DATE_TIME) %>%
  select(DATE_TIME,TEMP) %>%
  summarise(length = length(TEMP))
# A time tibble: 46 x 2
# Index: DATE_TIME
   DATE_TIME           length
   <dttm>               <int>
 1 1973-12-31 23:00:00   8760
 2 1974-12-31 23:00:00   8760
 3 1975-12-31 23:00:00   8760
 4 1976-12-31 23:00:00   8784
 5 1977-12-31 23:00:00   8760
 6 1978-12-31 23:00:00   8760
 7 1979-12-31 23:00:00   8760
 8 1980-12-31 23:00:00   8784
 9 1981-12-31 23:00:00   8760
10 1982-12-31 23:00:00   8760
# ... with 36 more rows

There is some minor variation…but that is due to leap years and the fact that this is showing us a calendar year instead of a solar year.

bf.tt %>% 
  collapse_by('hourly') %>%
  group_by(DATE_TIME) %>%
  select(DATE_TIME,TEMP) %>%
  summarise(count = n()) %>%
  collapse_by('yearly') %>%
  group_by(DATE_TIME) %>%
  summarise(min_val_per_hour = min(count),max_val_per_hour = max(count))
# A time tibble: 46 x 3
# Index: DATE_TIME
   DATE_TIME           min_val_per_hour max_val_per_hour
   <dttm>                         <dbl>            <dbl>
 1 1973-12-31 23:00:00             1.00             1.00
 2 1974-12-31 23:00:00             1.00             1.00
 3 1975-12-31 23:00:00             1.00             1.00
 4 1976-12-31 23:00:00             1.00             1.00
 5 1977-12-31 23:00:00             1.00             1.00
 6 1978-12-31 23:00:00             1.00             1.00
 7 1979-12-31 23:00:00             1.00             1.00
 8 1980-12-31 23:00:00             1.00             1.00
 9 1981-12-31 23:00:00             1.00             1.00
10 1982-12-31 23:00:00             1.00             1.00
# ... with 36 more rows

Now we have regularly spaced hourly data! Ready to move on.

Visualize Data and Make Adjustments as Needed

Plot Data and discuss

Temperature

A review of the graph shows a couple of things of interest. The first interesting thing is that there is a wide variation in daily temperatures, but it appears that there may be more variation between days during the winter than during the summer. It might be better to use daily Max/Min/Mean data for this analysis than to use hourly data.

Wind Speed

temp.xts <- xts(select(bf.tt,TEMP),order.by = bf.tt$DATE_TIME)

temp.xts %>% dygraph(main="Bowman Field Wind Direction (Compass Degrees)") %>%
  dyAxis('y', label = "Wind Direction (Compass Degrees)") %>%
  dySeries("DIR", axis = 'y') %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Determine what to do about NA values.

For most of the time series analysis the program needs to know how to handle NA values. Not an easy thing to do in some cases.

NA values in TEMP

It is pretty easy to handle NA values in TEMP. Most of the missing data is just an hour or two in a row. And temperature data is autocorrelated heavily with the last reading. So we can fill with extrapoloated values.

# bf.approx.tt <- bf.tt
# bf.approx.tt$TEMP <- trunc(zoo::na.approx(bf.tt$TEMP))

Perform Time Series Analysis

First Decomposition of Temperature

Since temperature is fairly clean data and easy to use a decomposition is done below.

# start <- bf.approx.tt[start(bf.approx.tt[,1][[1]])[1],1][[1]]
# bf.temp.ts <- ts(bf.approx.tt$TEMP, frequency = 24*365.25, start = start)
# 
# acf(bf.temp.ts)
# 
# temp.decomp <- decompose(bf.temp.ts, type = "multiplicative")
# par(mfrow=c(4,1))
# plot(temp.decomp$trend)
# plot(temp.decomp$seasonal)
# plot(temp.decomp$random)
# plot(temp.decomp$figure)
# 
# bf.temp.stl <- stl(bf.temp.ts, s.window="per", robust=TRUE)
# plot(bf.temp.stl)
# fcast <- forecast(bf.temp.stl, method = "naive")
# plot(fcast)

Reviewing Temperature Decomposition Results

This decomposition did not full account for the variation over time. The mean is well handled, but the random plot shows that the error terms are also seasonal.

Looking more closely at the data, there is variation each day from the low temperature to the high temperature. We can either deal with this by some fancy modelling…or we can break our temperature into daily low/mean/high values which will be easier to deal with.

Concerting TEMP into new variables

LS0tDQp0aXRsZTogIkxvdWlzdmlsbGUgV2VhdGhlciBBbmFseXNpcyINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQojIyBMb3Vpc3ZpbGxlIFdlYXRoZXIgVGltZSBTZXJpZXMgQW5hbHlzaXMNCg0KVGhlcmUgaXMgYSBsb3Qgb2YgbmV3cyBhYm91dCBjbGltYXRlIGNoYW5nZSBhbmQgc3RyYW5nZSB0ZW1wZXJhdHVyZXMgdGhpcyB5ZWFyLiBJbiBzb21lIGFyZWFzIHdpdGggaGF2ZSB0aGUgImJvbWIgY3ljbG9uZSIsIGFuZCBpbiBvdGhlciB3ZSBoYXZlIGRyb3VnaHQgY29uZGl0aW9ucy4gTGl2aW5nIGluIExvdWlzdmlsbGUgSSBkZWNpZGVkIHRvIHRha2Ugc29tZSB0aW1lIHRvIHNlZSB3aGF0IHRoZSBsb2NhbCBkYXRhIGFjdHVhbGx5IHNob3dzIGFib3V0IHRyZW5kcyBpbiB3ZWF0aGVyLiANCg0KRm9yIHRoaXMgYW5hbHlzaXMgSSB3aWxsIGJlIHVzaW5nIGEgZmV3IHBhY2thZ2VzIHdoaWNoIHlvdSB3aWxsIG5lZWQgbG9hZGVkIGluIG9yZGVyIHRvIGZvbGxvdyBhbG9uZzoNCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQojIExpc3Qgb2YgcGFja2FnZXMgdG8gbG9hZDoNCnBhY2thZ2VzIDwtIGMoInRpZHl2ZXJzZSIsICJsdWJyaWRhdGUiLCAidGliYmxldGltZSIsICJybGFuZyIsICJkeWdyYXBocyIsICJmb3JlY2FzdCIsICJ6b28iLCAieHRzIiwgInN0cmluZ3IiKQ0KICANCiMgQ2hlY2sgdG8gc2VlIHdoZXRoZXIgYW55IHBhY2thZ2VzIGFyZW4ndCBpbnN0YWxsZWQgb24gdGhlIGNvbXB1dGVyIGFuZCBpbnN0YWxsDQpuZXdfcGFja2FnZXMgPC0gcGFja2FnZXNbIShwYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywiUGFja2FnZSJdKV0NCmlmKGxlbmd0aChuZXdfcGFja2FnZXMpKSBpbnN0YWxsLnBhY2thZ2VzKG5ld19wYWNrYWdlcykNCiAgDQojIExvYWQgTmVjY2Vzc2FyeSBQYWNrYWdlcw0Kc2FwcGx5KHBhY2thZ2VzLCByZXF1aXJlLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQpybShuZXdfcGFja2FnZXMpDQpgYGANCg0KDQojIyBEYXRhIENvbGxlY3Rpb24gYW5kIEltcG9ydA0KDQpUaGUgTmF0aW9uYWwgQ2xpbWF0ZSBEYXRhIENlbnRlciAoTkNEQykgYW5kIE5hdGlvbmFsIENlbnRlcnMgZm9yIEVudmlyb25tZW50YWwgSW5mb3JtYXRpb24gKE5DRUkpIHByb3ZpZGVzIGFjY2VzcyB0byBob3VybHkgd2VhdGhlciBkYXRhIGZyb20gdGhlIE5hdGlvbmFsIE9jZWFuaWMgYW5kIEF0bW9zcGhlcmljIEFkbWluaXN0cmF0aW9uIChOT0FBKSBhdCBbaHR0cHM6Ly93d3c3Lm5jZGMubm9hYS5nb3YvQ0RPL2Nkb3BvZW1haW4uY21kP2RhdGFzZXRhYmJ2PURTMzUwNSZjb3VudHJ5YWJidj0mZ2VvcmVnaW9uYWJidj0mcmVzb2x1dGlvbj00MF0oaHR0cHM6Ly93d3c3Lm5jZGMubm9hYS5nb3YvQ0RPL2Nkb3BvZW1haW4uY21kP2RhdGFzZXRhYmJ2PURTMzUwNSZjb3VudHJ5YWJidj0mZ2VvcmVnaW9uYWJidj0mcmVzb2x1dGlvbj00MCkgKFZhbGlkIGFzIG9mIDAyLzA0LzIwMTggYnV0IHNsYXRlZCBmb3Igb2Jzb2xlY2Vuc2UgaW4gTWF5IG9mIDIwMTgpLiBUaGlzIGZvcm0gaXMgdGhlIGZhc3Rlc3Qgd2F5IHRvIGdldCBjbGVhbiBkYXRhLiBUaGUgbGluayBhdXRvbWF0aWNhbGx5IGNsZWFucyB0aGUgZGF0YSBpbnRvIGEgcGVyc29uIHJlYWRhYmxlIGZvcm1hdC4gU28gZm9yIGVhc2Ugb2YgdXNlIEkgcHV0IGluIGEgcmVxdWVzdCBmb3IgdGhlIGRhdGEgSSB3YW50ZWQgYW5kIHRoZW4gZG93bmxvYWRlZCB0aGUgdHh0IGZpbGVzLg0KDQoqIEJvd21hbiBGaWVsZCBBaXJwb3J0IGlzIFN0YXRpb24gSUQgNzI0MjM1MTM4MTAgYW5kIGhhcyBkYXRhIGZyb20gMTEvMTk0MSB0byAwMi8yMDE4IHdpdGggYSBnYXAgZnJvbSAyMDAwIHRvIDIwMDMuIA0KKiBCb3duYW0gRmxkIGlzIFN0YXRpb24gSUQgNzI0MjM1OTk5OTkgYW5kIGhhcyBkYXRhIGZyb20gMDEvMjAwMCB0byAxMi8yMDAzLg0KDQoqIExvdWlzdmlsbGUgU3RhbmRpZm9yZCBGaWVsZCBpcyBJRCA5OTk5OTk5MzgyMSBhbmQgaGFzIGRhdGEgZnJvbSAwMS8xOTQ4IHRvIDEyLzE5NzIuIA0KKiBMb3Vpc3ZpbGxlIEludGxfU3RhbmRpZm9yZCBGaWVsZCBBUCBpcyBJRCA3MjQyMzA5MzgyMSBhbmQgaGFzIGRhdGEgZnJvbSAwMS8xOTczIHRvIDAyLzIwMTgNCiogKioqV2FybmluZzogSSBhc2tlZCBhIGxvY2FsIGV4cGVydCBhYm91dCB0aGlzIHN0YXRpb24gYW5kIGhlIHNhaWQgdGhlIGdhZ2UgaXMgdG9vIGNsb3NlIHRvIHRoZSBhc3BoYWx0IGFuZCBnaXZlcyBiaWFzZWQgcmVhZGluZ3MuKioqDQoNCiogTGV4aW5ndG9uIEJsdWVncmFzcyBGaWVsZCBpcyBJRCA5OTk5OTk5MzgyMCBhbmQgaGFzIGRhdGEgZnJvbSAwMS8yOTQ4IHRvIDEyLzE5NzINCiogQmx1ZWdyYXNzIEFpcnBvcnQgaXMgSUQgNzI0MjIwOTM4MjAgYW5kIGhhcyBkYXRhIGZyb20gMDEvMTk3MyB0byAwMi8yMDE4LiANCiogKkxleGluZ3RvbiBpcyB0ZWNobmljYWxseSBvdXRzaWRlIG9mIExvdWlzdmlsbGUsIGJ1dCBwcm92aWRlcyBhIHJlYXNvbmFibHkgYW5hbG9nb3VzIGRhdGEgc2V0IHdoaWNoIG1heSBiZSBiZXR0ZXIgdGhhbiB1c2luZyB0aGUgTG91aXN2aWxsZSBBaXJwb3J0IGRhdGEgZHVlIHRvIHBsYWNlbWVudCBvZiB0aGUgZ2FnZXMuKg0KDQojIyMgRG93bmxvYWRpbmcgZGF0YSBmcm9tIHRoZSBzaXRlDQoNCkkgcHV0IGluIHRoZSBmb2xsb3dpbmcgcmVxdWVzdHMgb24gdGhlIHNpdGU6IA0KDQpGb3IgTGV4aW5ndG9uIERhdGENCiogVVMsIEtlbnR1Y2t5LCBCbHVlZ3Jhc3MgQWlycG9ydCwgMTk3M18wMV8wMV8wMCB0byAyMDA4XzAxXzAxXzIzLg0KDQpGb3IgQm93bWFuIEZpZWxkIERhdGEgdGhlcmUgaXMgc29tZSBzcGVjaWFsIHdvcmsgcmVxdWlyZWQgYmVjYXVzZSB0aGUgbmFtZSBvZiB0aGUgZmllbGQgY2hhbmdlZCBpbiB0aGUgIG1pZGRsZSBvZiB0aGUgZGF0YSBzZXQgZnJvbSAyMDAwIHRvIDIwMDMuDQoqIFVTLCBLZW50dWNreSwgQm93bWFuIEZsZCwgMjAwMF8wMV8wMV8wMCB0byAyMDAzXzEyXzMxXzIzLg0KKiBVUywgS2VudHVja3ksIEJvd21hbiBGaWxlZCBBaXJwb3J0LCAxOTczXzAxXzAxXzAwIHRvIDIwMDhfMDFfMDFfMjMuDQoNCkFmdGVyIGEgZmV3IG1pbnV0ZXMgdGhlIHdlYnNpdGUgcHJvY2Vzc2VkIHByb2Nlc3MgdGhlIGZpbGUgYW5kIHNlbmQgYW4gZW1haWwuIERvd25sb2FkIHRoZSBmaWxlIHRvIHRoZSB3b3JraW5nIGRpcmVjdG9yeSBhbmQgdGhlbiBpbXBvcnQgdGhlIGRhdGEgdG8gUi4NCg0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCiMgTG9hZCBMZXhpbmd0b24gV2VhdGhlciBEYXRhDQpsZXhpbmd0b24uZmlsZSA8LSBjKCI1MTU3MjI3NTQ3NDA2ZGF0LnR4dCIpDQpsZXguZGYgPC0gcmVhZC5kZWxpbShsZXhpbmd0b24uZmlsZSxoZWFkZXI9VFJVRSwgc2VwID0gIiIsIGFzLmlzID0gVFJVRSwgZmlsbCA9IFRSVUUpDQpybShsZXhpbmd0b24uZmlsZSkNCg0KIyBNYWtlIHRoZSBZUi4uTU9EQUhSTU4gZWFzaWVyIHRvIHJlYWQNCmNvbG5hbWVzKGxleC5kZilbM10gPC0gIkRBVEVfVElNRSINCg0KIyBMb2FkIEJvd21hbiBGaWVsZCBXZWF0aGVyIERhdGENCmJmLmZpbGUxIDwtIGMoIjE5NzNfMDFfMDEtMjAxOF8wMV8wMS1Cb3dtYW5GaWVsZC50eHQiKQ0KYmYuZmlsZTIgPC0gYygiMjAwMF8wMV8wMS0yMDAzXzEyXzMxLUJvd21hbkZpZWxkLnR4dCIpDQpiZi5kZjEgPC0gcmVhZC5kZWxpbShiZi5maWxlMSxoZWFkZXI9VFJVRSwgc2VwID0gIiIsIGFzLmlzID0gVFJVRSwgZmlsbCA9IFRSVUUpDQpiZi5kZjIgPC0gcmVhZC5kZWxpbShiZi5maWxlMixoZWFkZXI9VFJVRSwgc2VwID0gIiIsIGFzLmlzID0gVFJVRSwgZmlsbCA9IFRSVUUpDQpiZi5kZiA8LSBmdWxsX2pvaW4oYmYuZGYxLGJmLmRmMikNCnJtKGJmLmZpbGUxLGJmLmZpbGUyLGJmLmRmMSxiZi5kZjIpDQoNCiMgTWFrZSB0aGUgWVIuLk1PREFIUk1OIGVhc2llciB0byByZWFkDQpjb2xuYW1lcyhiZi5kZilbM10gPC0gIkRBVEVfVElNRSINCg0KIyBBcnJhbmdlIEJvd21hbiBGaWVsZCBEYXRhIGJ5IGRhdGUgYmVjYXVzZSB3ZSBhcmUgbWVyZ2luZyB0d28gc2V0cyBvZiBkYXRlIGRhdGENCiMgYW5kIG5lZWQgdGhlIGRhdGVzIHRvIGJlIGluIGFzY2VuZGluZyBvcmRlcg0KYmYuZGYgPC0gYmYuZGYgJT4lIGFycmFuZ2UoREFURV9USU1FKQ0KYGBgDQoNCiMjIyBDbGVhbiB1cCB2YWx1ZXMgdGhhdCBoYXZlIHNwZWNpYWwgbWVhbmluZw0KDQpTb21lIG9mIHRoZSB2YWx1ZXMgYXJlIG5vbi1udW1lcmljIGFuZCBoYXZlIHNwZWNpYWwgbWVhbmluZy4gDQoNCkluIHRoZSBjbG91ZCBjZWlsaW5nIGNvbHVtbiB0aGUgdmFsdWUgNzIyIG1lYW5zIHVubGltdGVkIChha2Egbm8gY2xvdWQgY2VpbGluZykuDQoNCkluIGFsbCBjb2x1bW5zIGEgKiByZXByZXNlbnRzIGFuIE5BIHZhbHVlLg0KDQpgYGB7cn0NCmNsZWFuX25jZGNfZGYgPC0gZnVuY3Rpb24oaW4uZGYpIHsNCiAgaW4uZGZbaW4uZGZbLCJDTEciXT09IjcyMiIsIkNMRyJdIDwtIEluZg0KDQogICMgUmVwbGFjZSBtaXNzaW5nIHZhbHVlcyBtYXJrZWQgYnkgKiB3aXRoIE5BDQogIGZvcihjb2wgaW4gbmFtZXMoaW4uZGYpKSB7DQogICAgaW4uZGZbLGNvbF0gPC0gc3RyX3JlcGxhY2VfYWxsKGluLmRmWyxjb2xdLCJeWypdKyQiLCJOQSIpDQogICAgaW4uZGZbaW4uZGZbLGNvbF09PSJOQSIsY29sXSA8LSBOQQ0KICB9DQogIHJldHVybihpbi5kZikNCn0NCg0KYmYuZGYgPC0gY2xlYW5fbmNkY19kZihiZi5kZikNCmxleC5kZiA8LSBjbGVhbl9uY2RjX2RmKGxleC5kZikNCg0Kcm0oY2xlYW5fbmNkY19kZikNCmBgYA0KDQojIyMgRml4IHZhcmlhYmxlIHR5cGVzIGluIHRoZSBkYXRhDQoNClR3byBvcGVyYXRpb25zIG5lZWQgdG8gYmUgcGVyZm9ybWVkIHRvIGZpeCB0aGUgdmFyaWFibGVzIHR5cGVzLg0KDQpGaXJzdCwgdGhlIERBVEVfVElNRSBmaWVsZCBzaG91bGQgYmUgaW4gZGF0ZSBmb3JtYXQuDQoNClNlY29uZCwgdGhlIG51bWVyaWMgaXRlbXMgc2hvdWxkIGJlIGluIG51bWVyaWMgZm9ybWF0DQoNCiogTm90ZTogQW55IGZpZWxkcyB3aXRoIGEgY2hhcmFjdGVyIHdpbGwgYmUgY29lcmNlZCB0byBOQS4gSW4gdGhpcyBjYXNlIGl0IGlzIGEgZ29vZCB0aGluZyBiZWNhdXNlIGEgY2hhcmFjdGVyIGluIGEgbnVtZXJpYyBmaWVsZCBpbmRpY2F0ZXMgdGhlcmUgd2FzIHNvbWUgYW5vbWFseSB3aXRoIHRoZSBkYXRhLg0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCmJmLmRmJERBVEVfVElNRSA8LSB5bWRfaG0oYmYuZGYkREFURV9USU1FKQ0KbGV4LmRmJERBVEVfVElNRSA8LSB5bWRfaG0obGV4LmRmJERBVEVfVElNRSkNCiAgDQojIFRoZSBmb2xsb3dpbmcgZmllbGRzIGFyZSBudW1iZXJpYyBkYXRhIChvciBjbG9zZSBlbm91Z2gpDQpudW1lcmljLnZhciA8LSBjKCJVU0FGIiwgIldCQU4iLCJESVIiLCJTUEQiLCJHVVMiLCJDTEciLCJWU0IiLCJURU1QIiwiREVXUCIsIkFMVCIsIlBDUDAxIiwiU0QiKQ0KZm9yKGNvbCBpbiBudW1lcmljLnZhcikgew0KICBiZi5kZltjb2xdIDwtIGFzLm51bWVyaWModW5saXN0KGJmLmRmW2NvbF0pKQ0KfQ0KZm9yKGNvbCBpbiBudW1lcmljLnZhcikgew0KICBsZXguZGZbY29sXSA8LSBhcy5udW1lcmljKHVubGlzdChsZXguZGZbY29sXSkpDQp9DQpybShudW1lcmljLnZhcikNCmBgYA0KDQojIyMgT25seSBrZWVwIHRoZSBjb2x1bW5zIG5lZWRlZCBmb3IgdGhlIGFuYWx5c2lzDQoNCkZvciB0aGlzIGFuYWx5c2lzIHdlIHdpbGwgb25seSBiZSB1c2luZyBudW1lcmljIGRhdGEuIFdlIHdpbGwga2VlcDoNCg0KKiBEYXRlL1RpbWUgKHRoZSB0aW1lIGluZGV4Li4uKQ0KKiBUZW1wZXJhdHVyZQ0KKiBEZXdwb2ludA0KKiBXaW5kIERpcmVjdGlvbg0KKiBXaW5kIFNwZWVkDQoqIEd1c3QgU3BlZWQNCiogQ2xvdWQgQ2VpbGluZw0KKiBWaXNpYmlsaXR5DQoqIFByZWNpcGl0YXRpb24gaW4gUHJldmlvdXMgSG91cg0KDQpgYGB7cn0NCmNvbHNUb0tlZXAgPC0gYygiREFURV9USU1FIiwiVEVNUCIsIkRFV1AiLCJESVIiLCJTUEQiLCJHVVMiLCJDTEciLCJWU0IiLCJQQ1AwMSIpDQpiZi5kZiA8LSBiZi5kZiAlPiUgc2VsZWN0KGNvbHNUb0tlZXApDQpsZXguZGYgPC0gbGV4LmRmICU+JSBzZWxlY3QoY29sc1RvS2VlcCkNCnJtKGNvbHNUb0tlZXApICANCmBgYA0KDQojIyMgTWFrZSB0aGlzIGludG8gYSB0aWJibGV0aW1lIG9iamVjdCBmb3IgYW5hbHlzaXMNCg0KQ29udmVydGluZyBpbnRvIGEgdGliYmxldGltZSBvYmplY3QgaXMgYXBwYXJlbnRseSB0aGUgbmV3IHdheSB0byB1c2UgdGltZSBzZXJpZXMgaW4gUiBhcyBvZiBlYXJseSAyMDE4LiANCg0KYGBge3J9DQpiZi50dCA8LSBhc190YmxfdGltZShiZi5kZixpbmRleCA9IERBVEVfVElNRSkNCmxleC50dCA8LSBhc190YmxfdGltZShsZXguZGYsIGluZGV4ID0gREFURV9USU1FKQ0KYGBgDQoNCk5vdyBsZXQncyBwbG90IHNvbWUgb2YgdGhlIGRhdGEgdG8gbWFrZSBzdXJlIHRoaXMgaXMgd29ya2luZy4gTGV0J3Mgc3RhcnQgd2l0aCB0ZW1wZXJhdHVyZSBiZWNhdXNlIGl0IGlzIHJlYWxhdGl2ZWx5IGNsZWFuIGRhdGEuDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMiwxKSkNCnBsb3QoeCA9IGJmLnR0JERBVEVfVElNRSwgeSA9IGJmLnR0JFRFTVAsIHR5cGUgPSAibiIsIG1haW4gPSAiQm93bWFuIEZpZWxkIikgKw0KICBsaW5lcyh4ID0gYmYudHQkREFURV9USU1FLCB5ID0gYmYudHQkVEVNUCkNCnBsb3QoeCA9IGxleC50dCREQVRFX1RJTUUsIHkgPSBsZXgudHQkVEVNUCwgdHlwZSA9ICJuIiwgbWFpbiA9ICJMZXhpbmd0b24iKSArDQogIGxpbmVzKHggPSBsZXgudHQkREFURV9USU1FLCB5ID0gbGV4LnR0JFRFTVApDQpgYGANCg0KSHV6emFoISBXZSBoYXZlIHR3byBzZXRzIG9mIGRhdGEgZnJvbSAxOTczIHRocm91Z2ggMjAxNyENCg0KIyMgUHJlcGFyZSBkYXRhIGZvciBUaW1lIFNlcmllcyBBbmFseXNpcw0KDQojIyMgTWFrZSB0aGUgZGF0YSBzZXQgcmVndWxhcg0KDQpNYW55IHRpbWUgc2VyaWVzIGZ1bmN0aW9ucyB1c2UgaW50ZWdlciBpbnB1dHMgdG8gZGV0ZXJtaW5lIHRoZSBsZW5ndGggb2YgYSBwZXJpb2QuIEZvciBleGFtcGxlOiBvbmUgeWVhciBpcyAzNjUgZGFpbHkgdmFsdWVzLCBvbmUgeWVhciBpcyAyNCozNjUuMjUgaG91cmx5IHZhbHVlcywgZXRjLiBCZWNhdXNlIHRoZXNlIGZ1bmN0aW9ucyB1c2UgYW4gaW50ZWdlciBudW1iZXIgb2YgdmFsdWVzIGFzIGEgcGVyaW9kLCBhbnkgbWlzc2luZyBvciBleHRyYSB2YWx1ZXMgd2lsbCBjYXVzZSBwcm9ibGVtcy4gRm9yIGluc3RhbmNlLCB3aGF0IGhhcHBlbnMgaWYgb25lIHllYXIgaGFzIGV4YWN0bHkgMzg1IGRhaWx5IHZhbHVlcywgYnV0IGFub3RoZXIgeWVhciBoYXMgMzAwIGRhaWx5IHZhbHVlcz8gSWYgd2UgcGVyZm9ybSBhIG1vdmluZyBhdmVyYWdlIHdpdGggYSB3aW5kb3cgb2YgYSAzNjUgZGF5cyB0aGVyZSB3aWxsIGJlIHByb2JsZW1zIGluIHRoZSAgbW92aW5nIGF2ZXJhZ2UuIA0KDQojIyMjIENoZWNrIGZvciByZWd1bGFyIGRhdGENCg0KTGV0J3MgZmlyc3Qgc2VlIGlmIHRoZXJlIGFyZSBhbnkgbWlzc2luZyBob3VycyBpbiBhIHllYXIuDQoNCmBgYHtyfQ0KYmYudHQgJT4lIA0KICBjb2xsYXBzZV9ieSgneWVhcmx5JykgJT4lDQogIGdyb3VwX2J5KERBVEVfVElNRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsVEVNUCkgJT4lDQogIHN1bW1hcmlzZShsZW5ndGggPSBsZW5ndGgoVEVNUCkpDQpgYGANCg0KTm90aWNlIHRoYXQgZWFjaCB5ZWFyIGhhcyBhIGRpZmZlcmVudCBsZW5ndGguIFRoaXMgZGF0YSBpcyBkZWZpbml0ZWx5IG5vdCByZWd1bGFyLiBUaGVyZSBpcyBhIGRpZmZlcmVuY2UgaW4gbGVuZ3RoIG9mIGFsbW9zdCAxLDAwMCBob3VycyBoZXJlLg0KDQpOb3cgbGV0J3Mgc2VlIGlmIHRoZXJlIGFyZSBhbnkgaG91cnMgd2l0aCBtdWx0aXBsZSByZWFkaW5ncy4NCg0KYGBge3J9DQpiZi50dCAlPiUgDQogIGNvbGxhcHNlX2J5KCdob3VybHknKSAlPiUNCiAgZ3JvdXBfYnkoREFURV9USU1FKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSxURU1QKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUNCiAgY29sbGFwc2VfYnkoJ3llYXJseScpICU+JQ0KICBncm91cF9ieShEQVRFX1RJTUUpICU+JQ0KICBzdW1tYXJpc2UobWluX3ZhbF9wZXJfaG91ciA9IG1pbihjb3VudCksbWF4X3ZhbF9wZXJfaG91ciA9IG1heChjb3VudCkpDQpgYGANCg0KQWdhaW4gb3VyIGRhdGEgaXNuJ3QgcmVndWxhciEgU29tZSBob3VycyBoYXZlIDEgdmFsdWUgYXNzb2NpYXRlZCB3aXRoIHRoZW0sIGFuZCBzb21lIGhhdmUgdXAgdG8gNSB2YWx1ZXMgYXNzb2NpYXRlZCB3aXRoIHRoZW0hDQoNCiMjIyMgRml4IGlycmVndWxhciBkYXRhDQoNClRoZSB3ZWF0aGVyIGRhdGEgaGFzIEJPVEggbWlzc2luZyBob3VybHkgdmFsdWVzIEFORCBleHRyYSBob3VybHkgdmFsdWVzLg0KDQpOb3csIGxldCdzIGZpbGwgaW4gYW55IG1pc3Npbmcgcm93cyBzbyB0aGF0IG91ciBwZXJpb2Qgb2Ygb25lIFtzb2xhcl0geWVhciBpcyBjb3JyZWN0LiANCg0KYGBge3J9DQojIyBOb3RlOiB0aGlzIGlzIG9sZCBjb2RlLiANCiMgTmVlZCB0byB1cGRhdGUgYW5kIHVzZSB0aWJibGV0aW1lOjpjcmVhdGVfc2VyaWVzKHN0YXJ0IH4gZW5kLCAnMSBob3VyJykNCnR0X2FkZF9taXNzaW5nX3Jvd3MgPC0gZnVuY3Rpb24oaW4udGJsX3RpbWUsIGJ5ID0gJ2hvdXInKSB7DQogICMgUHJvZ3JhbWF0aWNhbGx5IGZpbmQgdGhlIGluZGV4IGNvbHVtbg0KICBpbmRleF9jb2x1bW4gPC0gYXR0cmlidXRlcyhpbi50YmxfdGltZSkkaW5kZXhfcXVvW1syXV0NCiAgDQogICMgUHJvZ3JhbWF0aWNhbGx5IGZpbmQgdGhlIGZpcnN0IGFuZCBsYXN0IGRhdGUvdGltZSBpbiB0aGUgaW5kZXgNCiAgc3RhcnQuaWR4IDwtIHN0YXJ0KGluLnRibF90aW1lWyxwYXN0ZTAoaW5kZXhfY29sdW1uKV1bWzFdXSkNCiAgZW5kLmlkeCA8LSBlbmQoaW4udGJsX3RpbWVbLHBhc3RlMChpbmRleF9jb2x1bW4pXVtbMV1dKQ0KICANCiAgc3RhcnQgPC0gaW4udGJsX3RpbWVbc3RhcnQuaWR4LHBhc3RlMChpbmRleF9jb2x1bW4pXVtbMV1dW1sxXV0NCiAgZW5kIDwtIGluLnRibF90aW1lW2VuZC5pZHgscGFzdGUwKGluZGV4X2NvbHVtbildW1sxXV1bWzFdXQ0KICANCiAgc2VxLnZlYyA8LSBzZXEoc3RhcnQsZW5kLGJ5PWJ5KQ0KICBzZXEuZGYgPC0gZGF0YS5mcmFtZShzZXEudmVjKQ0KICAgICMgTWFrZSB0aGUgZGF0ZS90aW1lIGNvbHVtbiB0aGUgc2FtZSBhcyBmb3IgdGhlIG90aGVyIHZlY3RvciANCiAgICAjIHNvIHRoYXQgbWVyZ2UgYnkgd2lsbCBhdXRvIGRldGVjdC4NCiAgICBuYW1lcyhzZXEuZGYpWzFdIDwtIHBhc3RlKGluZGV4X2NvbHVtbikNCiAgDQogIG91dC50YmxfdGltZSA8LSBtZXJnZShpbi50YmxfdGltZSxzZXEuZGYsYWxsID0gVFJVRSkNCiAgb3V0LnRibF90aW1lIDwtIGFzX3RibF90aW1lKG91dC50YmxfdGltZSwgaW5kZXggPSBEQVRFX1RJTUUpDQp9DQoNCmJmLnR0IDwtIHR0X2FkZF9taXNzaW5nX3Jvd3MoYmYudHQsIGJ5ID0gJ2hvdXInKQ0KbGV4LnR0IDwtIHR0X2FkZF9taXNzaW5nX3Jvd3MobGV4LnR0LCBieSA9ICdob3VyJykNCmBgYA0KDQpXaXRoIGFsbCB0aGUgcm93cyB0aGVyZSwgbGV0J3MgcmVtb3ZlIGV4dHJhIGhvdXJseSB2YWx1ZXMgYnkgYWdncmVnYXRpbmcgdGhlIGhvdXIgdXNpbmcgYSBtZWFuIG9mIGFsbCB2YWx1ZXMgZm9yIHRoYXQgaG91ci4gKE5vdGU6IFRoZXJlIGFyZSBoYWxmIGEgbWlsbGlvbiBvYnNlcnZhdGlvbnMgaGVyZS4uLnRoaXMgdGFrZXMgYSBtaW51dGUgdG8gYWdncmVnYXRlLi4uKQ0KDQpgYGB7cn0NCnR0X3BlcmlvZF9hcHBseSA8LSBmdW5jdGlvbihpbi50YmxfdGltZSwgaW4ucGVyaW9kLCBpbi5mdW5jID0gbWVhbiwgbmFfcm0gPSBUUlVFKSB7DQogICMgUHJvZ3JhbWF0aWNhbGx5IGZpbmQgdGhlIGluZGV4IGNvbHVtbg0KICBpbmRleF9jb2x1bW4gPC0gYXR0cmlidXRlcyhpbi50YmxfdGltZSkkaW5kZXhfcXVvW1syXV0NCiAgIyBDb2xsYXBzZSBieSBwZXJpb2QsIGdyb3VwIGJ5IGluZGV4LCB0aGVuIHN1bW1hcmlzZSBieSBmdW5jdGlvbi4NCiAgb3V0LnRibF90aW1lIDwtIGluLnRibF90aW1lICU+JQ0KICAgIGNvbGxhcHNlX2J5KGluLnBlcmlvZCkgJT4lDQogICAgZ3JvdXBfYnkoISEhIHN5bShpbmRleF9jb2x1bW4pKSAlPiUNCiAgICBzdW1tYXJpc2VfaWYoaXMubnVtZXJpYywgaW4uZnVuYywgbmEucm0gPSBuYV9ybSkNCn0NCg0KYmYudHQgPC0gdHRfcGVyaW9kX2FwcGx5KGJmLnR0LCAnaG91cmx5JywgbWVhbiwgbmFfcm0gPSBUUlVFKQ0KbGV4LnR0IDwtIHR0X3BlcmlvZF9hcHBseShsZXgudHQsICdob3VybHknLCBtZWFuLCBuYV9ybSA9IFRSVUUpDQpgYGANCg0KDQojIyMjIENoZWNrIGFnYWluIGZvciByZWd1bGFyIGRhdGENCg0KTGV0J3MgdmFsaWRhdGUgdGhhdCB3ZSBub3cgaGF2ZSByZWd1bGFyIGRhdGEgKGZ1bGwgb2YgTkEncykuDQoNCmBgYHtyfQ0KYmYudHQgJT4lIA0KICBjb2xsYXBzZV9ieSgneWVhcmx5JykgJT4lDQogIGdyb3VwX2J5KERBVEVfVElNRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsVEVNUCkgJT4lDQogIHN1bW1hcmlzZShsZW5ndGggPSBsZW5ndGgoVEVNUCkpDQpgYGANCg0KVGhlcmUgaXMgc29tZSBtaW5vciB2YXJpYXRpb24uLi5idXQgdGhhdCBpcyBkdWUgdG8gbGVhcCB5ZWFycyBhbmQgdGhlIGZhY3QgdGhhdCB0aGlzIGlzIHNob3dpbmcgdXMgYSBjYWxlbmRhciB5ZWFyIGluc3RlYWQgb2YgYSBzb2xhciB5ZWFyLg0KDQpgYGB7cn0NCmJmLnR0ICU+JSANCiAgY29sbGFwc2VfYnkoJ2hvdXJseScpICU+JQ0KICBncm91cF9ieShEQVRFX1RJTUUpICU+JQ0KICBzZWxlY3QoREFURV9USU1FLFRFTVApICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBjb2xsYXBzZV9ieSgneWVhcmx5JykgJT4lDQogIGdyb3VwX2J5KERBVEVfVElNRSkgJT4lDQogIHN1bW1hcmlzZShtaW5fdmFsX3Blcl9ob3VyID0gbWluKGNvdW50KSxtYXhfdmFsX3Blcl9ob3VyID0gbWF4KGNvdW50KSkNCmBgYA0KDQpOb3cgd2UgaGF2ZSByZWd1bGFybHkgc3BhY2VkIGhvdXJseSBkYXRhISBSZWFkeSB0byBtb3ZlIG9uLg0KDQoNCg0KIyMgVmlzdWFsaXplIERhdGEgYW5kIE1ha2UgQWRqdXN0bWVudHMgYXMgTmVlZGVkDQoNCiMjIyBQbG90IERhdGEgYW5kIGRpc2N1c3MNCg0KIyMjIyBUZW1wZXJhdHVyZQ0KDQpgYGB7cn0NCnRlbXAueHRzIDwtIHh0cyhzZWxlY3QoYmYudHQsVEVNUCksb3JkZXIuYnkgPSBiZi50dCREQVRFX1RJTUUpDQoNCnRlbXAueHRzICU+JSBkeWdyYXBoKG1haW49IkJvd21hbiBGaWVsZCBUZW1wZXJhdHVyZSAoRikiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiVGVtcGVyYXR1cmUgKEYpIikgJT4lDQogIGR5U2VyaWVzKCJURU1QIiwgYXhpcyA9ICd5JykgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCmBgYA0KDQpBIHJldmlldyBvZiB0aGUgZ3JhcGggc2hvd3MgYSBjb3VwbGUgb2YgdGhpbmdzIG9mIGludGVyZXN0LiBUaGUgZmlyc3QgaW50ZXJlc3RpbmcgdGhpbmcgaXMgdGhhdCB0aGVyZSBpcyBhIHdpZGUgdmFyaWF0aW9uIGluIGRhaWx5IHRlbXBlcmF0dXJlcywgYnV0IGl0IGFwcGVhcnMgdGhhdCB0aGVyZSBtYXkgYmUgbW9yZSB2YXJpYXRpb24gYmV0d2VlbiBkYXlzIGR1cmluZyB0aGUgd2ludGVyIHRoYW4gZHVyaW5nIHRoZSBzdW1tZXIuIEl0IG1pZ2h0IGJlIGJldHRlciB0byB1c2UgZGFpbHkgTWF4L01pbi9NZWFuIGRhdGEgZm9yIHRoaXMgYW5hbHlzaXMgdGhhbiB0byB1c2UgaG91cmx5IGRhdGEuDQoNCiMjIyMgV2luZCBTcGVlZA0KDQpgYGB7cn0NCnRlbXAueHRzIDwtIHh0cyhzZWxlY3QoYmYudHQsVEVNUCksb3JkZXIuYnkgPSBiZi50dCREQVRFX1RJTUUpDQoNCnRlbXAueHRzICU+JSBkeWdyYXBoKG1haW49IkJvd21hbiBGaWVsZCBXaW5kIERpcmVjdGlvbiAoQ29tcGFzcyBEZWdyZWVzKSIpICU+JQ0KICBkeUF4aXMoJ3knLCBsYWJlbCA9ICJXaW5kIERpcmVjdGlvbiAoQ29tcGFzcyBEZWdyZWVzKSIpICU+JQ0KICBkeVNlcmllcygiRElSIiwgYXhpcyA9ICd5JykgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCmBgYA0KDQoNCiMjIyBEZXRlcm1pbmUgd2hhdCB0byBkbyBhYm91dCBOQSB2YWx1ZXMuDQoNCkZvciBtb3N0IG9mIHRoZSB0aW1lIHNlcmllcyBhbmFseXNpcyB0aGUgcHJvZ3JhbSBuZWVkcyB0byBrbm93IGhvdyB0byBoYW5kbGUgTkEgdmFsdWVzLiBOb3QgYW4gZWFzeSB0aGluZyB0byBkbyBpbiBzb21lIGNhc2VzLg0KDQojIyMjIE5BIHZhbHVlcyBpbiBURU1QDQoNCkl0IGlzIHByZXR0eSBlYXN5IHRvIGhhbmRsZSBOQSB2YWx1ZXMgaW4gVEVNUC4gTW9zdCBvZiB0aGUgbWlzc2luZyBkYXRhIGlzIGp1c3QgYW4gaG91ciBvciB0d28gaW4gYSByb3cuIEFuZCB0ZW1wZXJhdHVyZSBkYXRhIGlzIGF1dG9jb3JyZWxhdGVkIGhlYXZpbHkgd2l0aCB0aGUgbGFzdCByZWFkaW5nLiBTbyB3ZSBjYW4gZmlsbCB3aXRoIGV4dHJhcG9sb2F0ZWQgdmFsdWVzLiANCg0KYGBge3J9DQojIGJmLmFwcHJveC50dCA8LSBiZi50dA0KIyBiZi5hcHByb3gudHQkVEVNUCA8LSB0cnVuYyh6b286Om5hLmFwcHJveChiZi50dCRURU1QKSkNCmBgYA0KDQoNCg0KDQoNCg0KIyMgUGVyZm9ybSBUaW1lIFNlcmllcyBBbmFseXNpcw0KDQojIyMgRmlyc3QgRGVjb21wb3NpdGlvbiBvZiBUZW1wZXJhdHVyZQ0KDQpTaW5jZSB0ZW1wZXJhdHVyZSBpcyBmYWlybHkgY2xlYW4gZGF0YSBhbmQgZWFzeSB0byB1c2UgYSBkZWNvbXBvc2l0aW9uIGlzIGRvbmUgYmVsb3cuIA0KYGBge3IgZmlnLmhlaWdodCA9IDcsIGZpZy53aWR0aCA9IDh9DQojIHN0YXJ0IDwtIGJmLmFwcHJveC50dFtzdGFydChiZi5hcHByb3gudHRbLDFdW1sxXV0pWzFdLDFdW1sxXV0NCiMgYmYudGVtcC50cyA8LSB0cyhiZi5hcHByb3gudHQkVEVNUCwgZnJlcXVlbmN5ID0gMjQqMzY1LjI1LCBzdGFydCA9IHN0YXJ0KQ0KIyANCiMgYWNmKGJmLnRlbXAudHMpDQojIA0KIyB0ZW1wLmRlY29tcCA8LSBkZWNvbXBvc2UoYmYudGVtcC50cywgdHlwZSA9ICJtdWx0aXBsaWNhdGl2ZSIpDQojIHBhcihtZnJvdz1jKDQsMSkpDQojIHBsb3QodGVtcC5kZWNvbXAkdHJlbmQpDQojIHBsb3QodGVtcC5kZWNvbXAkc2Vhc29uYWwpDQojIHBsb3QodGVtcC5kZWNvbXAkcmFuZG9tKQ0KIyBwbG90KHRlbXAuZGVjb21wJGZpZ3VyZSkNCiMgDQojIGJmLnRlbXAuc3RsIDwtIHN0bChiZi50ZW1wLnRzLCBzLndpbmRvdz0icGVyIiwgcm9idXN0PVRSVUUpDQojIHBsb3QoYmYudGVtcC5zdGwpDQojIGZjYXN0IDwtIGZvcmVjYXN0KGJmLnRlbXAuc3RsLCBtZXRob2QgPSAibmFpdmUiKQ0KIyBwbG90KGZjYXN0KQ0KYGBgDQoNCiMjIyMgUmV2aWV3aW5nIFRlbXBlcmF0dXJlIERlY29tcG9zaXRpb24gUmVzdWx0cw0KDQpUaGlzIGRlY29tcG9zaXRpb24gZGlkIG5vdCBmdWxsIGFjY291bnQgZm9yIHRoZSB2YXJpYXRpb24gb3ZlciB0aW1lLiBUaGUgbWVhbiBpcyB3ZWxsIGhhbmRsZWQsIGJ1dCB0aGUgcmFuZG9tIHBsb3Qgc2hvd3MgdGhhdCB0aGUgZXJyb3IgdGVybXMgYXJlIGFsc28gc2Vhc29uYWwuDQoNCkxvb2tpbmcgbW9yZSBjbG9zZWx5IGF0IHRoZSBkYXRhLCB0aGVyZSBpcyB2YXJpYXRpb24gZWFjaCBkYXkgZnJvbSB0aGUgbG93IHRlbXBlcmF0dXJlIHRvIHRoZSBoaWdoIHRlbXBlcmF0dXJlLiBXZSBjYW4gZWl0aGVyIGRlYWwgd2l0aCB0aGlzIGJ5IHNvbWUgZmFuY3kgbW9kZWxsaW5nLi4ub3Igd2UgY2FuIGJyZWFrIG91ciB0ZW1wZXJhdHVyZSBpbnRvIGRhaWx5IGxvdy9tZWFuL2hpZ2ggdmFsdWVzIHdoaWNoIHdpbGwgYmUgZWFzaWVyIHRvIGRlYWwgd2l0aC4NCg0KIyMjIyBDb25jZXJ0aW5nIFRFTVAgaW50byBuZXcgdmFyaWFibGVzDQoNCg==